package com.linb.pan.server.modules.share.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.linb.pan.core.constants.BPanConstants;
import com.linb.pan.core.exception.BPanBusinessException;
import com.linb.pan.core.response.ResponseCode;
import com.linb.pan.core.utils.IdUtil;
import com.linb.pan.core.utils.JwtUtil;
import com.linb.pan.core.utils.UUIDUtil;
import com.linb.pan.server.common.config.PanServerConfig;
import com.linb.pan.server.common.stream.channel.PanChannels;
import com.linb.pan.server.common.stream.event.log.ErrorLogEvent;
import com.linb.pan.server.modules.file.constants.FileConstants;
import com.linb.pan.server.modules.file.context.CopyFileContext;
import com.linb.pan.server.modules.file.context.FileDownloadContext;
import com.linb.pan.server.modules.file.context.QueryFileListContext;
import com.linb.pan.server.modules.file.entity.BPanUserFile;
import com.linb.pan.server.modules.file.enums.DelFlagEnum;
import com.linb.pan.server.modules.file.service.IUserFileService;
import com.linb.pan.server.modules.file.vo.BPanUserFileVO;
import com.linb.pan.server.modules.share.constants.ShareConstants;
import com.linb.pan.server.modules.share.context.*;
import com.linb.pan.server.modules.share.entity.BPanShare;
import com.linb.pan.server.modules.share.entity.BPanShareFile;
import com.linb.pan.server.modules.share.enums.ShareDayTypeEnum;
import com.linb.pan.server.modules.share.enums.ShareStatusEnum;
import com.linb.pan.server.modules.share.mapper.BPanShareMapper;
import com.linb.pan.server.modules.share.service.IShareFileService;
import com.linb.pan.server.modules.share.service.IShareService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.linb.pan.server.modules.share.service.cache.ShareCacheService;
import com.linb.pan.server.modules.share.vo.*;
import com.linb.pan.server.modules.user.entity.BPanUser;
import com.linb.pan.server.modules.user.service.IUserService;
import com.linb.pan.stream.core.IStreamProducer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;


@Service
@Slf4j
public class ShareServiceImpl extends ServiceImpl<BPanShareMapper, BPanShare> implements IShareService {

    @Autowired
    @Qualifier(value = "shareManualCacheService")
    private ShareCacheService cacheService;

    @Autowired
    @Qualifier(value = "defaultStreamProducer")
    private IStreamProducer producer;

    @Autowired
    private PanServerConfig config;

    @Autowired
    private IShareFileService shareFileService;

    @Autowired
    private IUserFileService userFileService;

    @Autowired
    private IUserService userService;

    /**
     * 用于测试--查看所有人的分享列表
     */
    @Override
    public List<BPanShareUrlListVO> allShares() {
        List<BPanShare> records = list();
        List<BPanShareUrlListVO> voList = records.stream().map(r -> {
            BPanShareUrlListVO vo = new BPanShareUrlListVO();
            BeanUtils.copyProperties(r, vo);
            return vo;
        }).collect(Collectors.toList());
        return voList;
    }

    /**
     * 创建分享链接
     */
    @Override
    @Transactional
    public BPanShareUrlVO create(CreateShareUrlContext context) {
        //1.保存到b_pan_share
        saveShare(context);
        //2.保存到b_pan_share_file
        saveShareFiles(context);
        //3.拼装vo
        return assembleShareVO(context);
    }

    /**
     * 查看我的分享列表
     */
    @Override
    public List<BPanShareUrlListVO> getShares(QueryShareListContext context) {
        return baseMapper.selectShareVOListByUserId(context.getUserId());
    }

    /**
     * 取消分享
     */
    @Override
    @Transactional
    public void cancelShare(CancelShareContext context) {
        //1.校验权限
        checkUserCancelSharePermission(context);
        //2.取消分享记录
        doCancelShare(context);
        //3.取消分享文件关联记录
        doCancelShareFiles(context);
    }

    /**
     * 校验分享码
     */
    @Override
    public String checkShareCode(CheckShareCodeContext context) {
        //1.校验分享码状态
        BPanShare record = checkShareStatus(context.getShareId());
        context.setRecord(record);
        //2.校验分享码正确性
        doCheckShareCode(context);
        //3.设置token
        String token = generateShareToken(context);
        return token;
    }

    /**
     * 查询分享的详情
     */
    @Override
    public ShareDetailVO detail(QueryShareDetailContext context) {
        //1.校验分享状态
        BPanShare record = checkShareStatus(context.getShareId());
        context.setRecord(record);
        //2.初始化分享实体
        initShareVO(context);
        //3.查询分享的主体信息
        assembleMainShareInfo(context);
        //4.查询分享的文件列表
        assembleShareFilesInfo(context);
        //5.查询分享者信息
        assembleShareUserInfo(context);
        return context.getVo();
    }

    /**
     * 查询简单分享详情
     */
    @Override
    public ShareSimpleDetailVO simpleDetail(QueryShareSimpleDetailContext context) {
        //1.校验分享状态
        BPanShare record = checkShareStatus(context.getShareId());
        context.setRecord(record);
        //2.初始化分享实体(简单版)
        initShareSimpleVO(context);
        //3.查询分享的实体信息(简单版)
        assembleMainShareSimpleInfo(context);
        //4.查询分享者信息(简单版)
        assembleShareSimpleUserInfo(context);
        return context.getVo();
    }

    /**
     * 获取下一级文件列表
     */
    @Override
    public List<BPanUserFileVO> fileList(QueryChildFileListContext context) {
        //1.校验分享状态
        BPanShare record = checkShareStatus(context.getShareId());
        context.setRecord(record);
        //2.查询子文件(顺便判断fileId在分享列表中)
        List<BPanUserFileVO> allUserFileRecords = getAllShareUserFiles(context.getShareId(), Lists.newArrayList(context.getParentId()));
        //3.将子文件分组
        Map<Long, List<BPanUserFileVO>> parentFilesMap = allUserFileRecords.stream().collect(Collectors.groupingBy(BPanUserFileVO::getParentId));
        //4.获取对应的子文件夹 context.getParentId()
        List<BPanUserFileVO> voList = parentFilesMap.get(context.getParentId());
        if (CollectionUtils.isEmpty(voList)) {
            return Lists.newArrayList();
        }
        return voList;
    }

    /**
     * 保存分享文件
     */
    @Override
    public void saveFiles(ShareSaveContext context) {
        //1.校验分享状态
        checkShareStatus(context.getShareId());
        //2.判断文件是否在分享列表中
        checkFileIdOnShareStatus(context.getShareId(), context.getFileIdList());
        //3.复制操作
        doSaveFiles(context);
    }

    /**
     * 下载分享的文件
     */
    @Override
    public void download(ShareFileDownloadContext context) {
        //1.校验分享状态
        checkShareStatus(context.getShareId());
        //2.判断文件是否在分享列表中
        checkFileIdOnShareStatus(context.getShareId(), Lists.newArrayList(context.getFileId()));
        //3.下载操作
        doDownload(context);
    }

    /**
     * 刷新状态
     */
    @Override
    public void refreshShareStatus(List<Long> fileIdList) {
        //查询受影响的shareId集合
        List<Long> shareIdList = getShareIdListByFileIdList(fileIdList);
        if (CollectionUtils.isEmpty(shareIdList)) {
            return;
        }
        //去重
        HashSet<Long> shareIdSet = Sets.newHashSet(shareIdList);
        //刷新状态操作
        shareIdSet.stream().forEach(this::refreshOneShareStatus);
    }


    @Override
    public boolean removeById(Serializable id) {
        //return super.removeById(id);
        return cacheService.removeById(id);
    }

    @Override
    public boolean removeByIds(Collection<? extends Serializable> idList) {
        //return super.removeByIds(idList);
        return cacheService.removeByIds(idList);
    }

    @Override
    public boolean updateById(BPanShare entity) {
        //return super.updateById(entity);
        return cacheService.updateById(entity.getShareId(), entity);
    }

    @Override
    public boolean updateBatchById(Collection<BPanShare> entityList) {
        if (CollectionUtil.isEmpty(entityList)) {
            return true;
        }
        Map<String, BPanShare> entityMap = entityList.stream()
                .collect(Collectors.toMap(BPanShare::getShareCode, e -> e));
        //return super.updateBatchById(entityList);
        return cacheService.updateByIds(entityMap);
    }

    @Override
    public BPanShare getById(Serializable id) {
        //return super.getById(id);
        return cacheService.getById(id);
    }

    @Override
    public List<BPanShare> listByIds(Collection<? extends Serializable> idList) {
        //return super.listByIds(idList);
        if (CollectionUtil.isEmpty(idList)) {
            return Lists.newArrayList();
        }
        return cacheService.getByIds(idList);
    }

    /*=========================================private=========================================*/

    /**
     * 保存到b_pan_share
     */
    private void saveShare(CreateShareUrlContext context) {
        BPanShare record = new BPanShare();
        record.setShareId(IdUtil.get());
        record.setShareName(context.getShareName());
        record.setShareType(context.getShareType());
        record.setShareDayType(context.getShareDayType());
        Integer shareDay = ShareDayTypeEnum.getShareDayByCode(context.getShareDayType());
        if (shareDay.equals(-1)) {
            throw new BPanBusinessException("非法天数");
        }
        record.setShareDay(shareDay);
        record.setShareEndTime(DateUtil.offsetDay(new Date(), shareDay));
        record.setShareUrl(createShareUrl(record.getShareId()));
        record.setShareCode(createShareCode());
        record.setShareStatus(ShareStatusEnum.NORMAL.getCode());
        record.setCreateUser(context.getUserId());
        record.setCreateTime(new Date());
        if (!save(record)) {
            throw new BPanBusinessException("保存分享链接失败");
        }
        context.setRecord(record);
    }

    /**
     * 生成随机的4位字符串
     */
    private String createShareCode() {
        return RandomStringUtils.randomAlphabetic(4).toLowerCase();
    }

    /**
     * 创建分享的url
     */
    private String createShareUrl(Long shareId) {
        if (Objects.isNull(shareId)) {
            throw new BPanBusinessException("分享id不存在");
        }
        String sharePrefix = config.getSharePrefix();
        //没有斜杠结尾，就加斜杠
        if (sharePrefix.lastIndexOf(BPanConstants.SLASH_STR) == BPanConstants.MINUS_ONE_INT.intValue()) {
            sharePrefix = sharePrefix + BPanConstants.SLASH_STR;
        }
        //对shareId加密
        //return sharePrefix + shareId;
        return sharePrefix + URLEncoder.encode(IdUtil.encrypt(shareId));
    }

    /**
     * 保存到b_pan_share_file
     */
    private void saveShareFiles(CreateShareUrlContext context) {
        SaveShareFilesContext saveShareFilesContext = new SaveShareFilesContext();
        saveShareFilesContext.setShareId(context.getRecord().getShareId());
        saveShareFilesContext.setShareFileIdList(context.getShareFileIdList());
        saveShareFilesContext.setUserId(context.getUserId());
        shareFileService.saveShareFiles(saveShareFilesContext);
    }

    /**
     * 拼装BPanShareUrlVO
     */
    private BPanShareUrlVO assembleShareVO(CreateShareUrlContext context) {
        BPanShareUrlVO vo = new BPanShareUrlVO();
        BPanShare record = context.getRecord();

        vo.setShareId(record.getShareId());
        vo.setShareName(record.getShareName());
        vo.setShareUrl(record.getShareUrl());
        vo.setShareCode(record.getShareCode());
        vo.setShareStatus(record.getShareStatus());

        return vo;
    }

    /**
     * 校验取消分享的权限
     */
    private void checkUserCancelSharePermission(CancelShareContext context) {
        List<Long> shareIdList = context.getShareIdList();
        Long userId = context.getUserId();
        List<BPanShare> records = listByIds(shareIdList);
        if (CollectionUtils.isEmpty(records)) {
            throw new BPanBusinessException("分享不存在");
        }
        for (BPanShare record : records) {
            if (!Objects.equals(record.getCreateUser(), userId)) {
                throw new BPanBusinessException("不能取消他人分享的文件");
            }
        }
    }

    /**
     * 取消分享记录
     */
    private void doCancelShare(CancelShareContext context) {
        List<Long> shareIdList = context.getShareIdList();
        if (!removeByIds(shareIdList)) {
            throw new BPanBusinessException("取消分享记录失败");
        }
    }

    /**
     * 取消分享文件关联记录
     */
    private void doCancelShareFiles(CancelShareContext context) {
        QueryWrapper<BPanShareFile> queryWrapper = Wrappers.query();
        queryWrapper.eq("create_user", context.getUserId());
        queryWrapper.in("share_id", context.getShareIdList());
        if (!shareFileService.remove(queryWrapper)) {
            throw new BPanBusinessException("取消分享文件关联记录失败");
        }
    }

    /**
     * 校验分享码状态
     */
    private BPanShare checkShareStatus(Long shareId) {
        BPanShare record = getById(shareId);
        //判断分享是否存在
        if (Objects.isNull(record)) {
            throw new BPanBusinessException(ResponseCode.SHARE_CANCELLED);
        }
        //判断分享状态是否为删除
        if (Objects.equals(record.getShareStatus(), ShareStatusEnum.FILE_DELETED.getCode())) {
            throw new BPanBusinessException(ResponseCode.SHARE_FILE_MISS);
        }
        //如果是永久生效则返回
        if (Objects.equals(record.getShareDayType(), ShareDayTypeEnum.PERMANENT_VALIDITY.getCode())) {
            return record;
        }
        //如果不是永久生效需要校验时间
        if (record.getShareEndTime().before(new Date())) {
            throw new BPanBusinessException(ResponseCode.SHARE_EXPIRE);
        }
        return record;
    }

    /**
     * 校验分享码正确性
     */
    private void doCheckShareCode(CheckShareCodeContext context) {
        String shareCode = context.getShareCode();
        if (!Objects.equals(shareCode, context.getRecord().getShareCode())) {
            throw new BPanBusinessException("分享码错误");
        }
    }

    /**
     * 分享码生成token
     */
    private String generateShareToken(CheckShareCodeContext context) {
        BPanShare record = context.getRecord();
        String token = JwtUtil.generateToken(UUIDUtil.getUUID(), ShareConstants.SHARE_ID,
                record.getShareId(), ShareConstants.ONE_HOUR_LONG);
        return token;
    }

    /**
     * 初始化分享详情VO实体
     */
    private void initShareVO(QueryShareDetailContext context) {
        ShareDetailVO vo = new ShareDetailVO();
        context.setVo(vo);
    }

    /**
     * 查询分享的主体信息
     */
    private void assembleMainShareInfo(QueryShareDetailContext context) {
        ShareDetailVO vo = context.getVo();
        BPanShare record = context.getRecord();
        BeanUtils.copyProperties(record, vo);
        //此时vo中还缺bPanUserFileVOList和shareUserInfoVO
    }

    /**
     * 查询分享的文件列表
     */
    private void assembleShareFilesInfo(QueryShareDetailContext context) {
        Long shareId = context.getShareId();
        //查询文件id集合
        List<Long> fileIdList = getShareFileIdList(shareId);
        //补充QueryFileListContext
        QueryFileListContext queryFileListContext = new QueryFileListContext();
        queryFileListContext.setUserId(context.getRecord().getCreateUser());
        queryFileListContext.setDelFlag(DelFlagEnum.NO.getCode());
        queryFileListContext.setFileIdList(fileIdList);
        //根据文件id查询文件
        List<BPanUserFileVO> bPanUserFileVOList = userFileService.getFileList(queryFileListContext);
        context.getVo().setBPanUserFileVOList(bPanUserFileVOList);
    }

    /**
     * 根据shareId查询fileIdList
     */
    private List<Long> getShareFileIdList(Long shareId) {
        QueryWrapper<BPanShareFile> queryWrapper = Wrappers.query();
        queryWrapper.select("file_id");
        queryWrapper.eq("share_id", shareId);
        return shareFileService.listObjs(queryWrapper, value -> (Long) value);
    }

    /**
     * 查询分享者信息
     */
    private void assembleShareUserInfo(QueryShareDetailContext context) {
        Long userId = context.getRecord().getCreateUser();
        BPanUser record = userService.getById(userId);
        if (Objects.isNull(record)) {
            throw new BPanBusinessException("分享者不存在");
        }
        ShareUserInfoVO vo = new ShareUserInfoVO();
        vo.setUserId(userId);
        //用户名需要加密
        vo.setUsername(encryptUsername(record.getUsername()));
        context.getVo().setShareUserInfoVO(vo);
    }

    /**
     * 给用户名加密
     */
    private String encryptUsername(String username) {
        StringBuffer stringBuffer = new StringBuffer(username);
        stringBuffer.replace(2, username.length() - 2, BPanConstants.COMMON_ENCRYPT_STR);
        return stringBuffer.toString();
    }

    /**
     * 初始化分享实体(简单版)
     */
    private void initShareSimpleVO(QueryShareSimpleDetailContext context) {
        ShareSimpleDetailVO vo = new ShareSimpleDetailVO();
        context.setVo(vo);
    }

    /**
     * 查询分享的实体信息(简单版)
     */
    private void assembleMainShareSimpleInfo(QueryShareSimpleDetailContext context) {
        ShareSimpleDetailVO vo = context.getVo();
        vo.setShareId(context.getShareId());
        vo.setShareName(context.getRecord().getShareName());
    }

    /**
     * 查询分享者信息(简单版)
     */
    private void assembleShareSimpleUserInfo(QueryShareSimpleDetailContext context) {
        Long userId = context.getRecord().getCreateUser();
        BPanUser record = userService.getById(userId);
        if (Objects.isNull(record)) {
            throw new BPanBusinessException("分享者不存在");
        }
        ShareUserInfoVO vo = new ShareUserInfoVO();
        vo.setUserId(userId);
        //用户名需要加密
        vo.setUsername(encryptUsername(record.getUsername()));
        context.getVo().setShareUserInfoVO(vo);
    }

    /**
     * 查询子文件(顺便判断fileId在分享列表中)
     */
    private List<BPanUserFileVO> getAllShareUserFiles(Long shareId, List<Long> fileIdList) {
        //根据shareId获取fileIdList
        List<Long> shareFileIdList = getShareFileIdList(shareId);
        if (CollectionUtils.isEmpty(shareFileIdList)) {
            return Lists.newArrayList();
        }
        //调用userFile的递归查询子文件，获取所有文件记录
        List<BPanUserFile> allFileRecords = userFileService.findAllFileRecordsByFileIdList(shareFileIdList);
        //过滤，非空，非删除状态
        allFileRecords = allFileRecords.stream().filter(Objects::nonNull)
                .filter(record -> Objects.equals(record.getDelFlag(), DelFlagEnum.NO.getCode()))
                .collect(Collectors.toList());
        //提取fileId列表
        List<Long> allFileIdList = allFileRecords.stream().map(BPanUserFile::getFileId).collect(Collectors.toList());
        //判断fileId列表是否包含fileIdList
        if (allFileIdList.containsAll(fileIdList)) {
            //如果包含说明fileId在分享列表中，BPanUserFile需要转换为BPanUserFileVO
            return userFileService.transferVOList(allFileRecords);
        }
        throw new BPanBusinessException(ResponseCode.SHARE_FILE_MISS);
    }

    /**
     * 判断文件是否在分享列表中
     */
    private void checkFileIdOnShareStatus(Long shareId, List<Long> fileIdList) {
        getAllShareUserFiles(shareId, fileIdList);
    }

    /**
     * 复制操作
     */
    private void doSaveFiles(ShareSaveContext context) {
        CopyFileContext copyFileContext = new CopyFileContext();
        copyFileContext.setUserId(context.getUserId());
        copyFileContext.setFileIdList(context.getFileIdList());
        copyFileContext.setTargetParentId(context.getTargetParentId());
        userFileService.copy(copyFileContext);
    }

    /**
     * 下载操作
     */
    private void doDownload(ShareFileDownloadContext context) {
        FileDownloadContext downloadContext = new FileDownloadContext();
        downloadContext.setFileId(context.getFileId());
        downloadContext.setUserId(context.getUserId());
        downloadContext.setResponse(context.getResponse());
        //不需要所属用户才能下载
        userFileService.downloadWithoutUser(downloadContext);
    }

    /**
     * 根据文件id集合查找分享id集合
     */
    private List<Long> getShareIdListByFileIdList(List<Long> fileIdList) {
        QueryWrapper<BPanShareFile> queryWrapper = Wrappers.query();
        queryWrapper.select("share_id");
        queryWrapper.in("file_id", fileIdList);
        List<Long> shareIdList = shareFileService.listObjs(queryWrapper, value -> (Long) value);
        return shareIdList;
    }

    /**
     * 刷新状态操作
     */
    private void refreshOneShareStatus(Long shareId) {
        //1.查询分享信息，判断是否存在
        BPanShare record = getById(shareId);
        if (Objects.isNull(record)) {
            return;
        }
        //2.判断分享文件的父文件夹是否均为正常
        ShareStatusEnum shareStatus = ShareStatusEnum.NORMAL;
        boolean isAllNormal = checkShareFileAvailable(shareId);
        //如果有一个不正常，把分享状态变为删除
        if (!isAllNormal) {
            log.info("分享状态改为删除");
            shareStatus = ShareStatusEnum.FILE_DELETED;
        }
        //判断当前分享记录状态，如果状态没变化就不需要操作
        if (Objects.equals(record.getShareStatus(), shareStatus.getCode())) {
            log.info("不需要重复刷新状态");
            return;
        }
        //有变化则更新状态
        //doChangeShareStatus(shareId, shareStatus);
        doChangeShareStatus(record, shareStatus);
    }

    /**
     * 校验分享文件的状态是否全为正常
     */
    private boolean checkShareFileAvailable(Long shareId) {
        //根据shareId查找对应shareFileId
        List<Long> shareFileIdList = getShareFileIdList(shareId);
        for (Long fileId : shareFileIdList) {
            if (!checkUpFileAvailable(fileId)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 递归判断文件状态是否正常
     */
    private boolean checkUpFileAvailable(Long fileId) {
        BPanUserFile record = userFileService.getById(fileId);
        //出口1:查不到记录
        if (Objects.isNull(record)) {
            return false;
        }
        //出口2:删除状态
        if (Objects.equals(DelFlagEnum.YES.getCode(), record.getDelFlag())) {
            return false;
        }
        //出口3:顶级文件夹
        if (Objects.equals(record.getParentId(), FileConstants.TOP_PARENT_ID)) {
            //如果已经递归到了顶级文件夹，状态又是正常，就判断所有文件状态都为正常
            return true;
        }
        return checkUpFileAvailable(record.getParentId());
    }

    /**
     * 更新分享状态
     */
    /*private void doChangeShareStatus(Long shareId, ShareStatusEnum shareStatus) {
        UpdateWrapper<BPanShare> updateWrapper = Wrappers.update();
        updateWrapper.eq("share_id", shareId);
        updateWrapper.set("share_status", shareStatus.getCode());
        if (!update(updateWrapper)) {
            String msg = "更新分享状态失败，shareId = " + shareId + ", shareStatus = " + shareStatus;
            ErrorLogEvent event = new ErrorLogEvent(this, msg, 0L);
            applicationContext.publishEvent(event);
        } else {
            log.info("成功更新分享状态");
        }
    }*/

    /**
     * 更新分享状态 缓存版
     */
    private void doChangeShareStatus(BPanShare record, ShareStatusEnum shareStatus) {
        record.setShareStatus(shareStatus.getCode());
        if (!updateById(record)) {
            String msg = "更新分享状态失败，shareId = " + record.getShareId() + ", shareStatus = " + shareStatus;
            ErrorLogEvent event = new ErrorLogEvent(msg, 0L);
            producer.sendMessage(PanChannels.ERROR_LOG_OUTPUT, event);
        }
    }

}
